#ifndef SSD1306_HPP
#define SSD1306_HPP

#include "spi_driver.hpp"
#include "driver/spi_master.h"
#include "driver/i2c_master.h"
#include "driver/gpio.h"
#include <cstdbool>
#define OLED_CONTROL_BYTE_CMD_SINGLE    0x80
#define OLED_CONTROL_BYTE_CMD_STREAM    0x00
#define OLED_CONTROL_BYTE_DATA_SINGLE   0xC0
#define OLED_CONTROL_BYTE_DATA_STREAM   0x40

// Fundamental commands (pg.28)
#define OLED_CMD_SET_CONTRAST           0x81    // follow with 0x7F
#define OLED_CMD_DISPLAY_RAM            0xA4
#define OLED_CMD_DISPLAY_ALLON          0xA5
#define OLED_CMD_DISPLAY_NORMAL         0xA6
#define OLED_CMD_DISPLAY_INVERTED       0xA7
#define OLED_CMD_DISPLAY_OFF            0xAE
#define OLED_CMD_DISPLAY_ON             0xAF

// Addressing Command Table (pg.30)
#define OLED_CMD_SET_MEMORY_ADDR_MODE   0x20
#define OLED_CMD_SET_HORI_ADDR_MODE     0x00    // Horizontal Addressing Mode
#define OLED_CMD_SET_VERT_ADDR_MODE     0x01    // Vertical Addressing Mode
#define OLED_CMD_SET_PAGE_ADDR_MODE     0x02    // Page Addressing Mode
#define OLED_CMD_SET_COLUMN_RANGE       0x21    // can be used only in HORZ/VERT mode - follow with 0x00 and 0x7F = COL127
#define OLED_CMD_SET_PAGE_RANGE         0x22    // can be used only in HORZ/VERT mode - follow with 0x00 and 0x07 = PAGE7

// Hardware Config (pg.31)
#define OLED_CMD_SET_DISPLAY_START_LINE 0x40
#define OLED_CMD_SET_SEGMENT_REMAP_0    0xA0    
#define OLED_CMD_SET_SEGMENT_REMAP_1    0xA1    
#define OLED_CMD_SET_MUX_RATIO          0xA8    // follow with 0x3F = 64 MUX
#define OLED_CMD_SET_COM_SCAN_MODE      0xC8    
#define OLED_CMD_SET_DISPLAY_OFFSET     0xD3    // follow with 0x00
#define OLED_CMD_SET_COM_PIN_MAP        0xDA    // follow with 0x12
#define OLED_CMD_NOP                    0xE3    // NOP

// Timing and Driving Scheme (pg.32)
#define OLED_CMD_SET_DISPLAY_CLK_DIV    0xD5    // follow with 0x80
#define OLED_CMD_SET_PRECHARGE          0xD9    // follow with 0xF1
#define OLED_CMD_SET_VCOMH_DESELCT      0xDB    // follow with 0x30

// Charge Pump (pg.62)
#define OLED_CMD_SET_CHARGE_PUMP        0x8D    // follow with 0x14

// Scrolling Command
#define OLED_CMD_HORIZONTAL_RIGHT       0x26
#define OLED_CMD_HORIZONTAL_LEFT        0x27
#define OLED_CMD_CONTINUOUS_SCROLL      0x29
#define OLED_CMD_DEACTIVE_SCROLL        0x2E
#define OLED_CMD_ACTIVE_SCROLL          0x2F
#define OLED_CMD_VERTICAL               0xA3

#define I2C_ADDRESS 0x3C
#define SPI_ADDRESS 0xFF

class SSD1306 {

typedef enum {
    SCROLL_RIGHT = 1,
    SCROLL_LEFT = 2,
    SCROLL_DOWN = 3,
    SCROLL_UP = 4,
    PAGE_SCROLL_DOWN = 5,
    PAGE_SCROLL_UP = 6,
    SCROLL_STOP = 7
} ScrollType;

typedef struct {
    bool _valid; // Not using it anymore
    int _segLen; // Not using it anymore
    uint8_t _segs[128];
} PAGE_t;


typedef struct {
    i2c_master_bus_handle_t busHandle;  /*!< I2C bus handle */
    i2c_device_config_t i2cConfig;      /*!< Configuration for eeprom device */
    uint8_t addrWordlen;                /*!< block address wordlen */
    uint8_t writeTimeMs;                /*!< eeprom write time, typically 10ms*/
}SSD1306DriverConfig;

public:
    SSD1306();
    SSD1306(int width, int height);
    ~SSD1306();

    void init(int mosi, int sclk, spi_host_device_t spi_host, int cs, int dc, int reset);
    void init(SSD1306DriverConfig *config);

    int getWidth() const;
    int getHeight() const;
    int getPages() const;
    void showBuffer();
    void setBuffer(uint8_t *buffer);
    void getBuffer(uint8_t *buffer);
    void setPage(int page, uint8_t *buffer);
    void getPage(int page, uint8_t *buffer);
    void displayImage(int page, int seg, uint8_t *images, int width);
    void displayText(int page, const char *text, int text_len, bool invert);
    void clearScreen(bool invert);
    void clearLine(int page, bool invert);
    void setContrast(int contrast);
    void softwareScroll(int start, int end);
    void scrollText(const char *text, int text_len, bool invert);
    void scrollClear();
    void hardwareScroll(ScrollType scroll);
    void wrapAround(ScrollType scroll, int start, int end, int8_t delay);
    void bitmaps(int xpos, int ypos, uint8_t *bitmap, int width, int height, bool invert);
    void fadeOut();
    void rotateImage(uint8_t *image, bool flip);
    void displayRotateText(int seg, const char *text, int text_len, bool invert);
    void dump() const;
    void dumpPage(int page, int seg) const;

private:
    void hardwareInit();
    void _spiHWInit();
    void _i2cHWInit();
    void writeCommand(uint8_t cmd);
    void writeData(uint8_t *data, size_t len);
    void pixel(int xpos, int ypos, bool invert);
    void line(int x1, int y1, int x2, int y2, bool invert);
    void circle(int x0, int y0, int r, bool invert);
    void cursor(int x0, int y0, int r, bool invert);
    void invert(uint8_t *buf, size_t blen);
    void flip(uint8_t *buf, size_t blen);
    uint8_t copyBit(uint8_t src, int srcBits, uint8_t dst, int dstBits);
    uint8_t rotateByte(uint8_t ch1);
    void spiContrast(int contrast);
    void i2cContrast(int contrast);
    void spiHardwareScroll(ScrollType scroll);
    void i2cHardwareScroll(ScrollType scroll);
    void spiDisplayImage(int page, int seg, uint8_t *images, int width);
    void i2cDisplayImage(int page, int seg, uint8_t *images, int width);
    int _address;
    int _width;
    int _height;
    int _pages;
    int _dc;
    bool _scEnable;
    int _scStart;
    int _scEnd;
    int _scDirection;
    PAGE_t _page[8];
    bool _flip;
    SPIDriver spiDriver;
    SSD1306DriverConfig i2cDriver;
    i2c_master_dev_handle_t mI2CDev;
};

#endif // SSD1306_HPP
